Skip to content

fix: force serverless target #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 25, 2021
Merged

fix: force serverless target #343

merged 5 commits into from
May 25, 2021

Conversation

ascorbic
Copy link
Contributor

Uses an env var to emulate Vercel, then another to force the target to serverless if it's not set as serverless or experimental-serverless-trace

@github-actions github-actions bot added the type: bug code to address defects in shipped code label May 24, 2021
@ascorbic ascorbic requested a review from lindsaylevine May 24, 2021 10:45
@lindsaylevine
Copy link

Fixes #204 :D

Copy link

@lindsaylevine lindsaylevine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just need to update the tests :) plus few comments

delete require.cache[require.resolve('next/dist/next-server/server/config')]

// Clear memoized cache
getNextConfig.clear()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have to clear here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is memoized, so it will return the old value if we don't clear it

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works.
In the long run, we probably should consider removing memoization on getNextConfig() and passing its return value around function calls instead.

index.js Outdated
}
}
// Populates the correct config if needed
await doesNotNeedPlugin({ netlifyConfig, packageJson, failBuild })

// Because we memoize nextConfig, we need to do this after the write file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can prob remove this comment now i think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double ping on this one?

Suggested change
// Because we memoize nextConfig, we need to do this after the write file

@@ -12,9 +11,27 @@ const hasCorrectNextConfig = async ({ nextConfigPath, failBuild }) => {
const isValidTarget = acceptableTargets.includes(target)
if (!isValidTarget) {
console.log(
`Your next.config.js must set the "target" property to one of: ${acceptableTargets.join(', ')}. Update the
target property to allow this plugin to run.`,
`The "target" config property must be one of "${acceptableTargets.join('", "')}". Setting it to "serverless".`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Settingit to serverless" might imply to some that we're actually mutating/updating/changing their config file (which i don't think we're doing? 🤔 ). maybe "forcing the build to use target serverless" or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

@ascorbic
Copy link
Contributor Author

@ehmicky Can you think of a way for us to detect if the plugin is returning early when we're in tests? You mentioned here that we shouldn't return values in plugin methods.

@ascorbic
Copy link
Contributor Author

@lindsaylevine what do you think about disabling those tests for now until we can work out a better way of checking if it's run?

@lindsaylevine
Copy link

@ascorbic we can def remove the next export one. how about setting an env variable at the end of each step and checking that? i dont see a problem w that 🤔

// https://github.com/vercel/next.js/blob/canary/packages/next/telemetry/ci-info.ts

delete require.cache[require.resolve('next/dist/telemetry/ci-info')]
delete require.cache[require.resolve('next/dist/next-server/server/config')]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering next is performing some logic at require()-time, this approach makes sense.

The only alternative I can think of would be to call getNextConfig() in a separate process. However, this would add some significant complexity. Also, this would JSON-serialize the Next.js configuration, which would not work if the config includes non-JSON values (can it?), unless we use serialization: 'advanced' but that option is not available in Node 10.

Two potential problems we might have in the future though are:

  • I am unsure whether this would work with ES modules since the current Node.js implementation does not allow removing cached URLs (it allows passing query parameters as cache key, but this would not work here), so this might require mixing CommonJS require() and ES modules import in order to keep using require.cache.
  • This relies on next internal file structure. The cache deletion would be a noop if they choose to rename those files. Some things we could do to fix this:
    • Run some automated tests confirming those files exist with the latest version of next
    • delete all the next entries in require.cache instead of those specific files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we're already relying on the internal layout as we're using unpublished exports to get the config in the first place

@lindsaylevine
Copy link

lindsaylevine commented May 25, 2021 via email

// We emulate Vercel so that we can set target to serverless if needed
process.env.NOW_BUILDER = true
// If no valid target is set, we use an internal Next env var to force it
process.env.NEXT_PRIVATE_TARGET = 'serverless'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe some automated tests might be using the same process, i.e. running one test would change process.env for other tests. We could probably fix this by either:

  • Spawning those tests in different processes
  • Loading next right away so it uses those environment variables, then deleting process.env.NOW_BUILDER and process.env.NEXT_PRIVATE_TARGET (or restoring them to their original value).

@ehmicky
Copy link

ehmicky commented May 25, 2021

@ehmicky Can you think of a way for us to detect if the plugin is returning early when we're in tests? You mentioned here that we shouldn't return values in plugin methods.

I would personally suggest either:

  • Running a unit test instead of an integration test, i.e. importing the specific function to test instead of running the whole plugin
  • Testing a side effect produced by the plugin returning early.

@ascorbic
Copy link
Contributor Author

I worked it out. I extracted the target updating code into a different funciton, so it wasn't combined with the checks which I then run before. In the tests I check whether process.env.NEXT_PRIVATE_TARGET has been set, which will only happen if the checks have passed.

@ascorbic ascorbic requested a review from lindsaylevine May 25, 2021 15:43
Copy link

@lindsaylevine lindsaylevine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 💯

/* eslint-enable fp/no-delete, node/no-unpublished-require */
}

module.exports = verifyBuildTarget

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love this 🙏

console.log(
`The "target" config property must be one of "${acceptableTargets.join(
'", "',
)}". Building with "serverless" target.`,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE

index.js Outdated
}
}
// Populates the correct config if needed
await doesNotNeedPlugin({ netlifyConfig, packageJson, failBuild })

// Because we memoize nextConfig, we need to do this after the write file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double ping on this one?

Suggested change
// Because we memoize nextConfig, we need to do this after the write file

delete require.cache[require.resolve('next/dist/telemetry/ci-info')]
delete require.cache[require.resolve('next/dist/next-server/server/config')]

getNextConfig.clear()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:O omg. we couldve done this the whole time...................

test/index.js Outdated
@@ -89,14 +85,15 @@ describe('preBuild()', () => {
})

test('run plugin if the app has next export in an unused script', async () => {
process.env.hi = 'ok'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah. oops

test/index.js Outdated

expect(await pathExists('next.config.js')).toBeTruthy()
expect(process.env.NEXT_PRIVATE_TARGET).toBe('serverless')
// expect(await pathExists('next.config.js')).toBeTruthy()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// expect(await pathExists('next.config.js')).toBeTruthy()

@@ -133,7 +130,7 @@ describe('preBuild()', () => {
utils,
})

expect(await pathExists('next.config.js')).toBeFalsy()
expect(process.env.NEXT_PRIVATE_TARGET).toBeUndefined()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brilliant

@ascorbic ascorbic requested a review from lindsaylevine May 25, 2021 15:58
@ascorbic ascorbic merged commit b43b49f into main May 25, 2021
@ascorbic ascorbic deleted the mk/force-serverless branch May 25, 2021 16:37
serhalp pushed a commit that referenced this pull request Apr 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug code to address defects in shipped code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants